Zookeeper 是什么
Zookeeper 是什么
zookeeper是一个强一致【不严格】的分布式数据库,由多个节点共同组成一个分布式集群,挂掉任意一个节点,数据库仍然可以正常工作,客户端 无感知故障切换。客户端向任意一个节点写入数据,其它节点可以立即看到最新的数据。

zookeeper 的内部是一个 key/value 存储引擎,key 是以树状的形式构成了一个多级的层次结构,每一个节点既可以存储数据,又可以作为一个目录存放下一级子节点。

zookeeper 提供了创建/修改/删除节点的 api,如果父节点没有创建,字节点会创建失败。如果父节点还有子节点,父节点不可以被删除。
zookeeper 和客户端之间以 socket 形式进行双向通讯,客户端可以主动调用服务器提供的 api,服务器可以主动向客户端推送事件。有多种事件可以 watch,比如节点的增删改,子节点的增删改,会话状态变更等。
zookeeper的事件有传递机制,字节点的增删改触发的事件会向上层依次传播,所有的父节点都可以收到字节点的数据变更事件,所以层次太深/子节点太多会给服务器的事件系统带来压力,节点分配要做好周密的规划。
zookeeper 满足了 CAP 定理的分区容忍性 P 和强一致性 C,牺牲了高性能 A【可用性蕴含性能】。
zookeeper 的存储能力是有限的,当节点层次太深/子节点太多/节点数据太大,都会影响数据库的稳定性。所以 zookeeper 不是一个用来做高并发高性能的数据库,zookeeper 一般只用来存储配置信息。
zookeeper 的读性能随着节点数量的提升能不断增加,但是写性能会随着节点数量的增加而降低,所以节点的数量不宜太多,一般配置成 3 个或者 5 个就可以了。

图中可以看出当服务器节点增多时,复杂度会随之提升。因为每个节点和其它节点之间要进行 p2p 的连接。3 个节点可以容忍挂掉 1 个节点, 5 个节点可以容忍挂掉 2 个节点。
客户端连接 zookeeper 时会选择任意一个节点保持长链接,后续通信都是通过这个节点进行读写的。如果该节点挂了,客户端会尝试去连接其它节点。
服务器会为每个客户端连接维持一个会话对象,会话的ID会保存在客户端。会话对象也是分布式的,意味着当一个节点挂掉了,客户端使用原有的会话 ID 去连接其它节点,服务器维持的会话对象还继续存在,并不需要重新创建一个新的会话。
如果客户端主动发送会话关闭消息,服务器的会话对象会立即删除。如果客户端不小心奔溃了,没有发送关闭消息,服务器的会话对象还会继续存在一段时间。这个时间是会话的过期时间,在创建会话的时候客户端会提供这个参数,一般是 10 到 30 秒。
也许你会问连接断开了,服务器是可以感知到的,为什么需要客户端主动发送关闭消息呢?
因为服务器要考虑网络抖动的情况,连接可能只是临时断开了。为了避免这种情况下反复创建和销毁复杂的会话对象以及创建会话后要进行的一系列事件初始化操作,服务器会尽量延长会话的生存时间。
zookeeper 的节点可以是持久化(Persistent)的,也可以是临时(Ephemeral)的。所谓临时的节点就是会话关闭后,会话期间创建的所有临时节点会立即消失。一般用于服务发现系统,将服务进程的生命期和 zookeeper 子节点的生命期绑定在一起,起到了实时监控服务进程的存活的效果。
zookeeper 还提供了顺序节点。类似于 mysql 里面的 auto_increment 属性。服务器会在顺序节点名称后自动增加自增的唯一后缀,保持节点名称的唯一性和顺序性。
还有一种节点叫保护(Protected)节点。这个节点非常特殊,但是也非常常用。在应用服务发现的场合时,客户端创建了一个临时节点后,服务器节点挂了,连接断开了,然后客户端去重连到其它的节点。因为会话没有关闭,之前创建的临时节点还存在,但是这个时候客户端却无法识别去这个临时节点是不是自己创建的,因为节点内部并不存储会话 ID 字段。所以客户端会在节点名称上加上一个 GUID 前缀,这个前缀会保存在客户端,这样它就可以在重连后识别出哪个临时节点是自己之前创建的了。
Sessions 判活
会话对于 ZooKeeper 的操作非常重要。会话中的请求按 FIFO 顺序执行(队列)。一旦客户端连接到服务器,将建立会话并向客户端分配会话ID 。
客户端以特定的时间间隔发送心跳以保持会话有效。如果 ZooKeeper 集合在超过服务器开启时指定的期间(会话超时)都没有从客户端接收到心跳,则它会判定客户端死机。
会话超时通常以毫秒为单位。当会话由于任何原因结束时,在该会话期间创建的临时节点也会被删除。
Watches 监听变化
监视是一种简单的机制,使客户端收到关于 ZooKeeper 集合中的更改的通知。客户端可以在读取特定 znode 时设置 Watches。Watches 会向注册的客户端发送任何 znode( 客户端注册表)更改的通知。
Znode 更改是与 znode 相关的数据的修改或 znode 的子项中的更改。只触发一次 watches。如果客户端想要再次通知,则必须通过另一个读取操作来完成。当连接会话过期时,客户端将与服务器断开连接,相关的 watches 也将被删除。
ZooKeeper 的层次命名空间
ZooKeeper 提供的名称空间与标准文件系统的名称空间非常相似。

ZooKeeper 的层次命名空间
在图中,首先有一个由 “/” 分隔的 znode(ZooKeeper 命名空间中的每个节点都由路径进行唯一标识)。在根目录下,有两个逻辑命名空间 config 和 workers。
- config 命名空间用于集中式配置管理;
- workers 命名空间用于命名。
在 config 命名空间下,每个 znode 最多可存储1MB的数据。除了父 znode 也可以存储数据。这种结构的主要目的是存储同步数据并描述 znode 的元数据。此结构称为 ZooKeeper 数据模型。
Znode 兼具文件和目录两种特点。既像文件一样维护着数据长度、元信息、ACL、时间戳等数据结构,又像目录一样可以作为路径标识的一部分。每个 Znode 由三个部分组成:
- stat:此为状态信息,描述该 Znode 版本、权限等信息。
- data:与该 Znode 关联的数据(节点 path 和 data 的关系就像 Java中 Map 的 Key 和 Value)
- children:该 Znode 下的节点
ZNode 的 类型
Znode被分为持久(persistent)节点,顺序(sequential)节点和临时(ephemeral)节点。
Znode 兼具文件和目录两种特点。既像文件一样维护着数据长度、元信息、ACL、时间戳等数据结构,又像目录一样可以作为路径标识的一部分。每个 Znode 由三个部分组成:
持久节点
即使在创建该特定 znode 的客户端断开连接后,持久节点仍然存在。默认情况下,除非另有说明,否则所有 znode 都是持久的。
临时节点
客户端活跃时,临时节点就是有效的。当客户端与 ZooKeeper 集合断开连接时,临时节点会自动删除。因此,只有临时节点不允许有子节点。如果临时节点被删除,则下一个合适的节点将填充其位置。
顺序节点
顺序节点可以是持久的或临时的。当一个新的 znode 被创建为一个顺序节点时,ZooKeeper 通过将 10位的序列号附加到原始名称来设置 znode的路径。例如,如果将具有路径 /myapp 的 znode 创建为顺序节点,则 ZooKeeper 会将路径更改为 /myapp0000000001,并将下一个序列号设置为 0000000002。
如下,创建的节点名称一样都是 /a 但是 zk 会自 动在后面加上 0000000001

如果两个顺序节点是同时创建的,那么 ZooKeeper 不会对每个 znode 使用相同的数字。顺序节点在锁定和同步中起重要作用。一般用它创建唯一的 id
常见应用场景
维护配置信息
Java 编程经常会遇到配置项,比如数据库的 url、schema、 user和 password 等。
通常这些配置项我们会放置在配置文件中,再将配置文件放置在服务器上当需要更改配置项时,需要去服务器上修改对应的配置文件。但是随着分布式系统的兴起,由于许多服务都需要使用到该配置文件,因此有必须保证该配置服务的高可用性和各台服务器上配置数据的一致性。通常会将配置文件部署在一个集群上
然而一个集群动辄上千台服务器,此时如果再一台台服务器逐个修改配置文件那将是非常繁琐且危险的的操作,因此就需要一种服务,能够高效快速且可靠地完成配置项的更改等操作,并能够保证各配置项在每台服务器上的数据一致性。
Zookeeper 就可以提供这样一种服务,其使用 Zab 这种一致性协议来保证一致性。 现在有很多开源项目使用 Zookeeper 来维护配置,比如在 hbase 中, 客户端就是连接一个 zookeeper,获得必要的 hbase集群的配置信息,然后才可以进一步操作。 还有在开源的消 息队列 kafka 中,也使用 Zookeeper 来维护 broker 的信息。在 alibaba 开源的 soa 框架 dubbo 中也广泛的使用 Zookeeper 管理一些配置 来实现服务治理。

分布式锁
一个集群是一个分布式系统,由多台服务器组成。为了提高并发度和可靠性,多台服务器上运行着同一种服务。当多个服务在运行时就需要协调各服务的进度,有时候需要保证当某个服务在进行某个操作时,其他的服务都不能进行该操作,即对该操作进行加锁,如果当前机器挂掉后,释放锁并 fail over 到其他的机器继续执行该服务。

集群管理
一个集群有时会因为各种软硬件故障或者网络故障,出现某些服务器挂掉而被移除集群而某些服务器加入到集群中的情况,zookeeper 会将这些服务器加入/移出的情况通知给集群中的其他正常工作的服务器,以及时调整存储和计算等任务的分配和执行等。此外 zookeeper 还会对故障的服务器做出诊断并尝试修复。

生成分布式唯一 ID
在过去的单库单表型系统中,通常可以使用数据库字段自带的 auto_increment 属性来自动为每条记录生成一个唯一的ID。 但是分库分表后,就无法在依靠数据库的 auto_increment 属性来唯一标识一条记录了。
此时我们就可以用 zookeeper 在分布式环境下生成全局唯一ID。
做法如下:每次要生成一个新 ID 时,创建一个持久顺序节点,创建操作返回的节点序号,即为新 ID,然后把比自己节点小的删除即可
Reference
ZooKeeper 官方文档 Zookeeper是什么? 漫画:什么是ZooKeeper? Zookeeper 数据结构详解 Zookeeper 概述 手把手教你使用Go基于zookeeper编写服务发现「原创」